一個再安全的智慧合約,如果只能透過命令列(CLI)或 Foundry 腳本來呼叫,對一般使用者來說是毫無意義的。在 Web3 的世界裡,我們需要一個漂亮且直覺的網頁介面來讓使用者與區塊鏈互動,這就是所謂的 DApp (Decentralized Application,去中心化應用程式)。
我將暫時放下 Solidity,回到前端開發者的主場,認識 Web3 前端開發的神兵利器:Ethers.js。
DApp 的底層架構:RPC 與 Ethers.js
在傳統的 Web2 網頁開發中,前端(React / Vue)會透過 HTTP 請求去打後端的 API 伺服器,後端再與資料庫溝通。
但在 Web3 的架構中,智慧合約就是你的後端,區塊鏈就是你的資料庫。
網頁前端要怎麼跟區塊鏈溝通呢?區塊鏈節點有對外開放一種叫做 RPC (Remote Procedure Call) 的介面。然而,手動去打 RPC 請求並處理複雜的資料編碼(ABI encoding)會讓人崩潰。因此,開發社群推出了 Ethers.js 這個 JavaScript 函式庫,它幫我們把所有複雜的底層通訊都包裝成了優雅好用的 JS 函數。
Ethers.js 的三大核心元件
要學好 Ethers.js,你只需要搞懂它世界裡的「三本柱」:
什麼是 ABI (Application Binary Interface)?
當我們把 Solidity 編譯並部署上鏈後,它會變成機器才看得懂的 Bytecode。前端的 JavaScript 根本不知道這個合約有哪些函數、需要傳什麼參數。
ABI 就是合約的「說明書」或「菜單」。它是一個 JSON 格式的陣列,詳細記錄了這個合約所有公開函數的名稱、參數型別與回傳值。每次編譯合約時,Foundry 或 Hardhat 都會自動幫你生成這份 ABI。在前端實作時,我們必須把這份 ABI 匯入進來。
實戰:用 Ethers.js 讀取鏈上數據
讓我們來寫一段最簡單的 Node.js/ 前端腳本,示範如何連線到區塊鏈並讀取我們在 Day 11 發行的 ERC20 代幣合約。
首先,安裝 Ethers.js (目前主流為 v6 版本):
'''
npm install ethers
'''
接著,撰寫連接程式碼:
'''
// 引入 ethers
const { ethers } = require("ethers");
// 1. 準備合約的 ABI (只挑選我們需要用到的函數即可)
const tokenABI = [
// 查詢餘額的唯讀函數
"function balanceOf(address owner) view returns (uint256)",
// 查詢代幣名稱的唯讀函數
"function name() view returns (string)"
];
// 2. 設定合約地址與 Provider
const contractAddress = "0xYourTokenContractAddressHere"; // 替換成你部署的合約地址
// 這裡我們使用公開的 RPC 節點 (例如以太坊主網或測試網),實務上常使用 Infura 或 Alchemy
const provider = new ethers.JsonRpcProvider("https://rpc.sepolia.org");
async function main() {
// 3. 建立 Contract 物件 (傳入地址、ABI 與 Provider)
// 注意:因為我們只傳入了 Provider,所以這個合約物件目前是「唯讀」的
const tokenContract = new ethers.Contract(contractAddress, tokenABI, provider);
console.log("正在連線至區塊鏈並讀取合約...");
// 4. 呼叫合約函數
const tokenName = await tokenContract.name();
console.log(`代幣名稱: ${tokenName}`);
// 查詢某個地址的餘額
const targetAddress = "0x1234567890123456789012345678901234567890";
const balance = await tokenContract.balanceOf(targetAddress);
// 注意:區塊鏈回傳的餘額是包含 18 位小數的大整數 (BigInt),需要轉換為人類可讀格式
const formattedBalance = ethers.formatUnits(balance, 18);
console.log(`該地址餘額: ${formattedBalance} 顆代幣`);
}
main();
'''
今天我們成功搭起了前端與區塊鏈溝通的橋樑,並透過 Provider 實現了唯讀狀態的資料抓取。
但是,一個真正的 DApp 不能只有「看」的功能,我們必須讓使用者能夠「操作」它。